{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "32a1df8b-2c47-40a6-8c19-a90191754583",
   "metadata": {},
   "source": [
    "### 1.定义列表读取数据文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "df02dfa5-a805-403e-a09c-858cac745fb4",
   "metadata": {},
   "outputs": [],
   "source": [
    "academy_titles = [] \n",
    "job_titles = []\n",
    "with open(\"academy_titles.txt\", encoding=\"utf-8\", mode=\"r\") as f:\n",
    "    for line in f:\n",
    "        academy_titles.append(line.strip())\n",
    "\n",
    "with open(\"job_titles.txt\", encoding=\"utf-8\", mode=\"r\") as f:\n",
    "    for line in f:\n",
    "        job_titles.append(line.strip())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e28dc6c4-cb50-4dec-8c2d-40f167534a57",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['北师教育学，你我一起努力，让胜利酣畅淋漓。',\n",
       " '考博英语词汇',\n",
       " '出售人大新闻学院2015年考研权威资料',\n",
       " '【脑科院 郭桃梅课题组】科研助理招聘',\n",
       " '管理学院的同学帮帮忙呐～']"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "academy_titles[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "980c93f1-1d7c-4690-bf75-3d2cc8d4e395",
   "metadata": {},
   "source": [
    "### 2.统计数据集中出现的字符数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "12994448-866d-4582-8ac4-5e22f3954e5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1570\n"
     ]
    }
   ],
   "source": [
    "char_set = set() #定义set集合用于字符去重\n",
    "for title in academy_titles:\n",
    "    for ch in title:\n",
    "        char_set.add(ch)\n",
    "\n",
    "for title in job_titles:\n",
    "    for ch in title:\n",
    "        char_set.add(ch)\n",
    "\n",
    "print(len(char_set))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1b521d9-75aa-4820-b884-553da3336f6f",
   "metadata": {},
   "source": [
    "### 3.词嵌入表示标题数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0481aeb8-9730-4ce0-9071-b35800b8262c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "char_list = list(char_set)\n",
    "n_chars = len(char_list) + 1 #<UNK>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "62aa6239-9e08-4cca-884d-32a51585b936",
   "metadata": {},
   "outputs": [],
   "source": [
    "def title_to_tensor(title):\n",
    "    tensor = torch.zeros(len(title), dtype=torch.long)\n",
    "    for li, ch in enumerate(title):\n",
    "        try:\n",
    "            ind = char_list.index(ch)\n",
    "        except ValueError:\n",
    "            ind = n_chars - 1\n",
    "        tensor[li] = ind\n",
    "    return tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f9812366-3d54-499b-8996-dbe981d73f95",
   "metadata": {},
   "outputs": [],
   "source": [
    "embedding = torch.nn.Embedding(n_chars, 100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8203737f-f637-497f-b954-5e54be580d0c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "招聘兼职/ 笔试考务 /200-300 每人\n"
     ]
    }
   ],
   "source": [
    "print(job_titles[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "255bea75-c8cb-45af-8a9b-f8def667e9b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([ 979, 1094, 1287,  184,  675, 1218, 1152,  932,  662,  188, 1218,  675,\n",
      "         357,  145,  145,  804,  251,  145,  145, 1218,  110, 1304])\n"
     ]
    }
   ],
   "source": [
    "print(title_to_tensor(job_titles[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "984e58c0-e8d3-4d44-892d-1d40256f7ab8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#print(title_to_tensor(job_titles[1])[1].item())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7b980ab-90da-4890-a3ce-bfdc15ef46cf",
   "metadata": {},
   "source": [
    "### 4.字符级别RNN模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "09c0142d-a37a-4905-9048-959bd80be759",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b540d699-dbed-4f77-b59c-fb0b86305d61",
   "metadata": {},
   "outputs": [],
   "source": [
    "#word_count 词表大小\n",
    "#embedding_size 词嵌入维度\n",
    "#hidden_size 隐藏层维度\n",
    "#output_size 输出维度\n",
    "\n",
    "class RNN(nn.Module): #定义模型继承自nn.Module\n",
    "    def __init__(self, word_count, embedding_size, hidden_size, output_size): #词表容量， 词嵌入维度， 隐藏层大小， 输出大小\n",
    "        super(RNN, self).__init__() #调用父类的构造函数\n",
    "        self.hidden_size = hidden_size\n",
    "        self.embedding = torch.nn.Embedding(num_embeddings = word_count, embedding_dim = embedding_size)\n",
    "        self.i2h = nn.Linear(embedding_size + hidden_size, hidden_size)\n",
    "        self.i2o = nn.Linear(embedding_size + hidden_size, output_size)\n",
    "        self.softmax = nn.LogSoftmax(dim=1) #softmax层\n",
    "        \n",
    "    def forward(self, input_tensor, hidden):\n",
    "        #print(\"input_tensor: \", input_tensor)\n",
    "        word_vector = self.embedding(input_tensor)\n",
    "        #print(\"word_vector: \", word_vector)\n",
    "        combine = torch.cat([word_vector, hidden], 1)\n",
    "        hidden = self.i2h(combine) #隐藏层输出\n",
    "        output = self.i2o(combine) #输出\n",
    "\n",
    "        output = self.softmax(output)\n",
    "        return output, hidden\n",
    "    \n",
    "    def initHidden(self): #初始化隐藏层的数据\n",
    "        return torch.zeros(1, self.hidden_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "548b6740-e776-4176-b3b0-1cc728fd85fb",
   "metadata": {},
   "source": [
    "### 5.简单运行模型测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "ef9e1b63-2f75-44f7-a691-2a15c3b0b64d",
   "metadata": {},
   "outputs": [],
   "source": [
    "embedding_size = 200\n",
    "n_hidden = 128\n",
    "n_categorizes = 2\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "rnn = RNN(n_chars, embedding_size, n_hidden, n_categorizes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f55df7b4-ae93-44e1-91c5-976eb220ef66",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'北师教育学，你我一起努力，让胜利酣畅淋漓。'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "academy_titles[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "51d1f3ba-54ea-4f70-b72e-1bc2fff57f88",
   "metadata": {},
   "outputs": [],
   "source": [
    "input_tensor = title_to_tensor(academy_titles[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "bbdc24af-83ed-4d70-bab3-bc7df48335cc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_tensor:\n",
      " tensor([1464, 1285,  496, 1171, 1260, 1268,  453, 1272, 1537, 1025, 1362,  533,\n",
      "        1268,  135,  313, 1251, 1392,  111,  427,  741, 1391])\n",
      "tensor(1464)\n",
      "tensor([1464])\n",
      "torch.Size([21])\n"
     ]
    }
   ],
   "source": [
    "print(\"input_tensor:\\n\", input_tensor)\n",
    "print(input_tensor[0]) #.unsqueeze(dim=0))\n",
    "print(input_tensor[0].unsqueeze(dim=0))\n",
    "print(input_tensor.size())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "5f7d64b8-e46b-4c70-a4a5-31b1d7bd3882",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "output:\n",
      " tensor([[-0.3105, -1.3208]], grad_fn=<LogSoftmaxBackward>)\n",
      "hidden:\n",
      " tensor([[ 0.2142,  0.7169,  0.2226,  0.1686,  0.5198,  0.6397,  0.0507, -0.1436,\n",
      "         -0.7081, -0.0714,  0.2588,  0.7722,  0.3533,  0.3136, -0.8421, -0.2427,\n",
      "         -0.5971, -0.0629, -0.9905, -0.1314,  0.3367,  0.4176,  0.7333, -0.1087,\n",
      "         -1.0817,  0.0948, -1.0033,  0.9521,  0.1898, -0.0182, -0.3684, -0.0378,\n",
      "         -0.2235,  0.0752,  0.1389, -0.2527,  0.2792, -0.3506, -0.9622, -0.0330,\n",
      "          0.2309, -0.2594, -0.2283,  0.0776,  1.0759,  0.4103, -0.4651, -0.7203,\n",
      "          0.5789, -0.6905,  0.4042, -0.2684, -0.2452, -0.2323, -0.0233, -0.0608,\n",
      "          0.4656, -0.6943,  0.0180,  1.0367,  0.2002,  0.0664, -0.0664, -0.0419,\n",
      "         -0.2325,  0.3486, -0.7654,  0.0544,  0.0057,  0.3908, -0.2414,  0.4918,\n",
      "         -0.2002, -0.5746,  0.0143, -0.5583, -0.6304, -0.7846, -0.2631, -0.2168,\n",
      "         -0.7741,  0.9337, -0.0883,  0.6030,  0.3038,  0.3621,  0.3514, -0.6306,\n",
      "          0.6345,  1.1057,  0.0526, -0.6480,  0.4951, -0.5053, -0.6489, -0.4334,\n",
      "          0.4325,  0.4549, -0.1253, -0.4394,  0.4202,  0.2534, -0.0922,  0.2200,\n",
      "          0.2364, -0.3173, -0.5251, -0.3888,  0.1289, -0.7909, -0.0419, -0.3790,\n",
      "          0.1460,  0.3275,  0.1580,  0.0511, -0.1488, -0.0613,  0.3927,  0.3740,\n",
      "         -0.1459,  0.8223, -0.0199, -0.0744,  0.1555, -0.0497,  0.9670, -0.1090]],\n",
      "       grad_fn=<AddmmBackward>)\n",
      "size of hidden:\n",
      " torch.Size([1, 128])\n"
     ]
    }
   ],
   "source": [
    "hidden = rnn.initHidden()\n",
    "output, hidden = rnn(input_tensor[0].unsqueeze(dim=0), hidden)  #RNN中每次输入一个字符\n",
    "\n",
    "print(\"output:\\n\", output)\n",
    "print(\"hidden:\\n\", hidden)\n",
    "print(\"size of hidden:\\n\", hidden.size())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5400002e-7257-4934-8ae4-367b3f84f298",
   "metadata": {},
   "source": [
    "### 6.数据预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "63c7c593-5f73-48d5-9c04-d83d2aaf4707",
   "metadata": {},
   "outputs": [],
   "source": [
    "#合并数据添加标签\n",
    "\n",
    "all_data = []\n",
    "categories = [\"考研考博\", \"招聘信息\"]\n",
    "for l in academy_titles:\n",
    "    all_data.append((title_to_tensor(l), torch.tensor([0], dtype = torch.long)))  #考研考博 0\n",
    "for l in job_titles:\n",
    "    all_data.append((title_to_tensor(l), torch.tensor([1], dtype=torch.long)))  #招聘信息 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "3259a2d5-f192-438e-aef6-60d8671f3ec0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "size of train_data:  4975\n",
      "size of test_data:  2133\n"
     ]
    }
   ],
   "source": [
    "#划分训练集和验证集\n",
    "\n",
    "import random\n",
    "random.shuffle(all_data)\n",
    "data_len = len(all_data)\n",
    "split_ratio = 0.7\n",
    "\n",
    "train_data = all_data[:int(data_len * split_ratio)]\n",
    "test_data = all_data[int(data_len * split_ratio):]\n",
    "\n",
    "print(\"size of train_data: \", len(train_data))\n",
    "print(\"size of test_data: \", len(test_data))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "0bc13e71-9ed9-41dc-af0d-64e9ec085a29",
   "metadata": {},
   "outputs": [],
   "source": [
    "#对于一句话循环使用字符级别的RNN模型\n",
    "\n",
    "def run_RNN(rnn, input_tensor):\n",
    "    hidden = rnn.initHidden()\n",
    "    for i in range(input_tensor.size()[0]):  #tensor.size() -> torch.Size([num]), 添加索引[0]获取数值\n",
    "        #hidden = hidden.to(device)\n",
    "        output, hidden = rnn(input_tensor[i].unsqueeze(dim=0), hidden)\n",
    "    return output"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd04355e-7b15-40d2-8daf-a73fe2e84df3",
   "metadata": {},
   "source": [
    "### 7.训练与评估设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2583ecfa-8840-4e28-9340-c3f7ce549426",
   "metadata": {},
   "outputs": [],
   "source": [
    "#模型的训练\n",
    "\n",
    "def train(rnn, criterion,input_tensor, category_tensor):\n",
    "    rnn.zero_grad()\n",
    "    output = run_RNN(rnn, input_tensor)\n",
    "    loss = criterion(output, category_tensor)\n",
    "    loss.backward()\n",
    "    \n",
    "    #根据梯度更新参数\n",
    "    for p in rnn.parameters():\n",
    "        p.data.add_(p.grad.data, alpha = -learning_rate)\n",
    "    \n",
    "    return output, loss.item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "c97f8377-07e9-4d26-a924-cb6ce3ff9ec2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#模型的评估\n",
    "\n",
    "def evaluate(rnn, input_tensor):\n",
    "    with torch.no_grad():\n",
    "        hidden = rnn.initHidden()\n",
    "        output = run_RNN(rnn, input_tensor)\n",
    "        \n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5801263d-3f95-4017-b9dd-12cd76c43486",
   "metadata": {},
   "source": [
    "### 8.训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "75290a55-b12b-4af3-bcdf-1277b144b71f",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████████████████████████████████████████████████████████████████████████| 4975/4975 [02:12<00:00, 37.46it/s]\n",
      "100%|█████████████████████████████████████████████████████████████████████████████| 2133/2133 [00:02<00:00, 757.01it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "accuracy 0.9854664791373652\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "from tqdm import tqdm\n",
    "epoch = 1\n",
    "embedding_size = 200\n",
    "n_hidden = 10\n",
    "n_categories = 2\n",
    "learning_rate = 0.005\n",
    "rnn = RNN(n_chars, embedding_size, n_hidden, n_categories)\n",
    "criterion = nn.NLLLoss()\n",
    "loss_sum = 0\n",
    "all_losses = []\n",
    "plot_every = 100\n",
    "for e in range(epoch):\n",
    "    for ind, (title_tensor, label) in enumerate(tqdm(train_data)):\n",
    "        output, loss = train(rnn, criterion, title_tensor, label)\n",
    "        loss_sum += loss\n",
    "        if ind % plot_every == 0:\n",
    "            all_losses.append(loss_sum / plot_every)\n",
    "            loss_sum = 0\n",
    "    c = 0\n",
    "    for title, category in tqdm(test_data):\n",
    "        output = evaluate(rnn, title)\n",
    "        topn, topi = output.topk(1)\n",
    "        if topi.item() == category[0].item():\n",
    "            c += 1\n",
    "    print('accuracy', c / len(test_data))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e537b6df-0bf3-4a6a-a1c0-3e8c2e0d5533",
   "metadata": {},
   "source": [
    "### 9.模型的保存与加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "18e0e6cc-1ce3-4b58-a256-5ca36acb2620",
   "metadata": {},
   "outputs": [],
   "source": [
    "#模型参数的保存\n",
    "# torch.save(rnn.state_dict(), \"rnn_parameter.pkl\")\n",
    "torch.save(rnn, \"rnn_model.pkl\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "4347c31a-5e63-48d5-8079-4837835b16b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#模型参数的加载(需要重新定义模型)\n",
    "# embedding_size = 200\n",
    "# n_hidden = 128\n",
    "# n_categories = 2\n",
    "# rnn = RNN(n_chars, embedding_size, n_hidden, n_categories)\n",
    "# rnn.load_state_dict(torch.load(\"rnn_parameter.pkl\"))\n",
    "rnn = torch.load(\"rnn_model.pkl\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "d6945c49-dfad-4945-a154-2f4974ef5b5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#保存词表\n",
    "import json\n",
    "\n",
    "with open(\"char_list.json\", mode=\"w\") as f:\n",
    "    json.dump(char_list, f)\n",
    "\n",
    "#加载词表\n",
    "with open(\"char_list.json\", mode=\"r\") as f:\n",
    "    char_list = json.load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be10a22f-4d8c-419a-8668-dc6849163846",
   "metadata": {},
   "source": [
    "### 10.测试应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "9ca9feb0-4447-4088-b9e7-ca27b545721a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_category(title):\n",
    "    title = title_to_tensor(title)\n",
    "    output = evaluate(rnn, title)\n",
    "    topn, topi = output.topk(1)\n",
    "    return categories[topi.item()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "ea3d185e-ddd0-49c1-a5f9-1fc17f12d8f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_test(title):\n",
    "    print((title, get_category(title)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "a066ea79-910d-4c39-8556-2415b5c96049",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('考研心得', '考研考博')\n",
      "('北大实验室博士', '考研考博')\n",
      "('校外博士招考', '招聘信息')\n",
      "('急求自然语言处理工程师', '招聘信息')\n",
      "('校招offer比较', '招聘信息')\n",
      "('工作还是考研', '考研考博')\n",
      "('工作吧', '考研考博')\n",
      "('招聘人员', '招聘信息')\n"
     ]
    }
   ],
   "source": [
    "print_test(\"考研心得\")\n",
    "print_test(\"北大实验室博士\")\n",
    "print_test(\"校外博士招考\")\n",
    "print_test(\"急求自然语言处理工程师\")\n",
    "print_test(\"校招offer比较\")\n",
    "print_test(\"工作还是考研\")\n",
    "print_test(\"工作吧\")\n",
    "print_test(\"招聘人员\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0613811-dd30-401d-8ad7-ea11f431ff17",
   "metadata": {},
   "source": [
    "### 完成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7edbb35c-d960-492e-838c-49757ac69725",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
